By
Darrell Brogdon
May 27, 2003
Intended
Audience Background What
Is A Template System? Separating
Content and Presentation Getting
The Content Security
Note Table
Templates Getting
the Navigation Putting
It All Together Conclusion About
the Author
Intended AudienceThe purpose of this
article is to show PHP’s inherent capabilities as a template
engine for developers who don’t wish to learn another
sub-language.
BackgroundMy last
article didn’t generate many flames so I thought I’d fan
the fire a little this time around. With all the new template
system projects that seem to be the rage these days, I find
myself questioning the need. To my mind PHP is the ultimate
template system. In this article I’ll lay out some ideas on
how to properly use PHP so that you simply won’t need another
template system.
What Is A Template System?Shortly after
Netscape released their browser versions with all its amazing
bells and whistles (inline graphics, background images,
...COLOR!), web developers and designers found themselves in a
bit of a quandary. How to separate the presentation from the
content? Without some way of dynamically generating the
content the designer might have to create a lot of versions of
the site for each page. Tedious even when creating the site,
and even more so when a change needed to be made. A change to
“my_cool_pic.jpg" on one page meant you had to change it on
all of them, by hand!
Remember, this was before the
days of Dreamweaver. Notepad just doesn’t cut it (unless
you’re a sadist).
So the designers turned to the
developers and demanded that they come up with a way of
generating the pages dynamically, on-the-fly. Developers like
a good problem needing to be solved when they see it, so they
dove right in with Perl (Perl was better than C for such
things, and PHP hadn’t been invented yet).
Perl being
- well - Perl, the developers soon found that there was still
much room for improvement. So they came up with things like
FastTemplates. The idea was to chop the page up into its
component parts (header, footer, navigation, body, etc...),
putting it all back together and dynamically including the
content when the page was called.
This was fine up to
a point, but designers know Photoshop and HTML, and don’t care
much for curly braces and command-lines. Conversely,
developers know curly braces but don’t care so much for
Photoshop, and grudgingly tolerate HTML as one of “those"
languages. When a designer decided that the background color
of the header navigation had a bit too much mauve (who names
these colors anyway?) she might not be able to simply edit an
HTML file. The use of Vi or Emacs might be involved <insert
scary violin noises here>. So the designer would ask the
developer to make the change for her, and the developer would
promptly quit in a huff and get another job making $20,000
more a year. Ah, the good old days...
Anyway, not long
after this dance had played itself out, a buzz about PHP was
beginning to stir. You mean you can mix the code and HTML?
Yep, and you can talk to a lot of databases, and it looks a
lot like C. Curly braces and everything!
Not what the
designers wanted to hear. Lets face it: designers want to
design things and developers want to write code.
Separating Content and PresentationSo out
pop projects like FastTemplates for PHP and Smarty whose real
purpose is (or should be) to separate content from
presentation. The only problem with these projects is that
designers still have to be developers. They still have to
learn the language that the template system implements.
For example, using Smarty you can include a file like
so:
{include file ="my_template.tpl" } Or you could use PHP
itself to include the file:
<?php
include("my_template.tpl" ); ?> There’s
not much difference between the two methods, in this example
it’s just a matter of an extra three characters. Well, from a
designer’s standpoint anyway, and the PHP option at least
looks somewhat like an HTML tag (with ‘<’ and ‘>’).
Including files isn’t nearly as off-putting as using
conditionals, however. Let’s see a conditional using Smarty.
(By the way, I’m not picking on Smarty for any particular
reason other than it seems to be the most popular of PHP-based
template systems. The price of fame and all that.)
{if $email eq "john@doe.com"}
Hello <b>John Doe</b>. {/if} And the
same in PHP:
<?php
if ($email == "john@doe.com") {
echo "Hello <b>John Doe</b>"; } ?>
Again, little difference but both are enough to make the
designer throw fits.
So in order to help appease both the designers and
developers I would like to suggest a solution that I’ve found
to be very simple yet very effective.
Working with a good designer you can structure the layout
of the site to be conducive for using as a template without
being elaborate or complex. Lets say you have a site that
requires a Header, Footer, Navigation, and Content sections.
It might look something like this:
Header |
Nav |
Content |
Footer | In
this scenario the likelihood of the Header and Footer changing
from page to page is probably less than the Nav and Content.
However, we still want to keep them all as separate
components. The HTML for this page might look something like
this:
<html> <head>
<title>My Web Site</title>
</head> <body>
<table>
<tr>
<td colspan="2" height="15%";>
<!-- HEADER -->
</td>
</tr>
<tr>
<td width="25%" height="80%">
<!-- NAVIGATION -->
</td>
<td width="75%" height="80%">
<!-- CONTENT -->
</td>
</tr>
<tr>
<td colspan="2" height="5%">
<!-- FOOTER -->
</td>
</tr>
</table> </body>
</html> Since this page is
generated dynamically, we need some way of determining which
content to display in the Content section. One of the simplest
ways is using URL parameters. So lets say we want to display
an “About" page with content containing information about our
web site, using a URL like this:
http://example.com/index.php?page=about
Getting The ContentWe can now use the
following PHP code in the Content section to properly display
the About page:
<td width="75%" height="80%">
<!-- CONTENT --> <?php if ($_GET['page'] == 'about') {
include('about.php');
}
?> </td> Or, better yet:
<td width="75%" height="80%">
<!--
CONTENT --> <?php
//
Assume the value of $_GET['page'] is the same
// as the file name we’re going to
include. include($_GET['page'] . '.php');
?> </td> This method has one major
problem and that is, how do we include the Home page when
$_GET['page'] has no value? Using a conditional,
we can determine if there is a value for
$_GET['page'] and include the home page if there
isn’t a value.
<td width="75%" height="80%">
<!--
CONTENT --> <?php
if (empty($_GET['page'])) {
include('home.php');
} else {
include($_GET['page'] . '.php');
}
?> </td> As good as this might be,
its still doesn’t meet the criteria of separation of content
and presentation. There is still way too much code for the
designer’s taste, and it would be best contained within a
function. So lets put it all inside a function, along with a
few additional features that might make life simpler for the
designer.
Security NoteYou should be aware that this
method of determining the content based on a
$_GET value is inherently dangerous. It is very
easy for someone to change the value and thus be able to view
a page you might not want them to see. Make sure you validate
any $_GET values before doing anything with them!
<?php
function getContent() {
if (empty($_GET['page'])) {
$page = 'home';
} else {
$page = $_GET['page'];
}
$page .= '.php';
if (false == is_file($page)) {
$page = 'file_not_found.php';
}
include($page); }
?> In addition to
automatically including either a named content or home page,
the above function also performs a few additional duties:
- First it’s cleaner and somewhat more efficient in that
there is only one “include()" call made.
- Second, it checks to see that the requested page
actually exists as a file. For example, if you were to call
this function with “page=foobar" and there wasn’t a
“foobar.php" file in that directory, then it would default
to a file named “file_not_found.php" (which should contain
an alert to the user that the file they requested wasn’t
found). Alternatively you could push out an HTTP 404 error,
if that was your preference (remember to use Output Buffering if you do).
Now
that we have encapsulated all of this inside a function let’s
see how it might look to the designer:
<td width="75%" height="80%">
<!--
CONTENT --> <?php getContent(); ?> </td> In addition to reducing
the code from ten lines to four we’ve made it much simpler
from the designer’s point of view. Granted, “<?php
getContent(); ?>" does look a bit odd compared to
most HTML code, but you can learn to live with it.
You
might be wondering how we got access to $_GET
from inside the function without defining it as global or
passing it as a function parameter. $_GET along
with $_POST, $_FILES, etc... are
considered “superglobals" meaning these arrays retain their
scope no matter where in the script they are referenced. Even
inside functions.
Table TemplatesAnother problem most people
will run into is how to display dynamic table data in a
template. Anyone who has done this in PHP knows that it can
get messy.
Lets assume we have “page=members_list" in
our URL string, meaning we’re going to be including a file
called “members_list.php". This page will contain a table that
lists miscellaneous information about a number of members such
as name, telephone number, etc... The data will be pulled from
a database of course and we need to make it so that the
designer can design the table and the developer can build the
logic.
So an example of the table from the designer’s
point of view might look something like this:
<table border="1">
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Telephone</th>
<?php getMembersList('Silver', 'White'); ?>
</table>
And here is the function definition for
getMembersList():
<?php
function getMembersList($bgcolor1, $bgcolor2) {
// ...
// SQL Query
// ...
$i = 0;
while ($row = mysql_fetch_array($result)) {
$bgcolor = ($i++ & 1) ? $bgcolor1 : $bgcolor2;
echo '<tr>';
echo '<td bgcolor=”' . $bgcolor . '">'
. $row['first_name']
. '</td>';
echo '<td bgcolor=”' . $bgcolor . '">'
. $row['last_name']
. '</td>';
echo '<td bgcolor=”' . $bgcolor . '">'
. $row['email']
. '</td>';
echo '<td bgcolor=”' . $bgcolor . '">'
. $row['telephone']
. '</td>';
echo '</tr>';
} } ?>
This function takes as its parameters two colors
that will be used for alternating table data background
colors. Then it performs a SQL query on the database to get
the members data. Finally it walks through the resulting array
returned by mysql_fetch_array() and using
echo() displays the HTML formatted member data.
Getting the NavigationNow that we’ve
simplified getting the content and seen how to build a table
template, lets have a look at the navigation template.
The basic structure is virtually identical to
getContent() except that with navigation there
are probably some things that we want to show/hide depending
on factors such as whether a user is logged in. Lets assume
that the following are the menu items we require:
- Home
- About
- Contact
- Log In/Log Out
If a user is logged out then it
should say “Log In". Otherwise it should say “Log Out". In
addition, if a user is logged in we want to show the following
navigation items:
- My Account
- Members Directory
All of this will be contained
within a function called getNav() and it looks
like this:
<?php
function getNav() {
echo '<a
href="/index.php">Home</a><br/>';
echo '<a
href="/index.php?page=about">About</a><br/>';
echo '<a
href="/index.php?page=contact">Contact</a><br/>';
if (empty($_COOKIE['user_id'])) {
echo '<a href="/index.php?page=login">Log
In</a><br/>';
} else {
echo '<a href="/index.php?page=logout">Log
Out</a><br/>';
echo '<a href="/index.php?page=my_account">My
Account</a><br/>';
echo '<a
href="/index.php?page=members_directory">Members
Directory</a><br/>';
} } ?>
In this function we’re mainly just using
echo() to display the hyperlinks. In addition,
we’re checking whether a user is logged in (assuming their
user ID is stored in a cookie if they are) and displaying the
appropriate links.
As with $_GET,
$_COOKIE is a superglobal so its automatically in
scope to the getNav() function.
Putting It All TogetherNow that we have
templates for the navigation and content areas of the page
lets see how the modified HTML might look:
<html> <head>
<title>My Site :<?php getTitle(); ?></title>
</head> <body>
<table>
<tr>
<td colspan="2" height="15%">
<!-- HEADER -->
<?php getHeader(); ?>
</td>
</tr>
<tr>
<td width="25%" height="80%">
<!-- NAVIGATION -->
<?php getNav(); ?>
</td>
<td width="75%" height="80%">
<!-- CONTENT -->
<?php getContent(); ?>
</td>
</tr>
<tr>
<td colspan="2" height="5%">
<!-- FOOTER -->
<?php getFooter(); ?>
</td>
</tr>
</table> </body>
</html> In addition to
functions for getting the Header and Footer, you probably
noticed one other function; getTitle(). Since
we’re determining which page to include with
getContent() its safe to assume that we would
want to update the page title accordingly. The
getTitle() function does just that:
<?php
function getTitle() {
echo ucfirst($_GET['page']); } ?>
This is a simple function that displays the value
of the “page" argument after capitalizing the first letter.
You’ll probably want to get fancier with your own version.
ConclusionIn this article you have seen
how to structure your web site so that you can utilize the
power of PHP as a template system.
Some of you
developers out there are probably groaning because you have to
write HTML code. When you get back from whatever alternate
universe doesn’t require you to use HTML when developing PHP
applications I hope you’ll have brought me a t-shirt. Lets
face it: most PHP applications are used for the web, and the
language of the web is HTML.
If you’re interested in
seeing a good, working, real-world example of using PHP
templates check out the b2 blogging system. Specifically, the
‘b2template.php’ file. They have a rich API for use in any custom templates
you might build and do a reasonably good job of separating
content from presentation (though they do let a little too
much PHP inside b2template.php for my tastes).
While I
hope you have learned something that you can use in your own
projects, this tutorial by no means covers all that there is
to web site templates. I encourage you to try out these
techniques and post any ideas and suggestions you have in the
comments section.
About the authorDarrell Brogdon is an
independent consultant specializing in web application
development. He can be reached at darrell@brogdon.net.
|